package com.zhf.cloud.gateway.springcloudgateway.filter.util;

import com.alibaba.fastjson.JSONObject;
import com.zhf.cloud.gateway.springcloudgateway.entity.LogRecord;
import com.zhf.cloud.gateway.springcloudgateway.entity.LoginRecord;
import com.zhf.cloud.gateway.springcloudgateway.feign.UserClient;
import com.zhf.cloud.gateway.springcloudgateway.filter.model.LogObj;
import com.zhf.cloud.gateway.springcloudgateway.filter.model.RequestInfo;
import com.zhf.cloud.gateway.springcloudgateway.filter.model.ResponsInfo;
import com.zhf.cloud.gateway.springcloudgateway.model.user.LoginAppUser;
import com.zhf.cloud.gateway.springcloudgateway.service.LogService;
import com.zhf.cloud.gateway.springcloudgateway.utils.AuthUtil;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;

/**
 * @author: 曾鸿发
 * @create: 2021-11-22 11:10
 * @description：
 **/
@Component
public class GatewayLogUtil {

    @Autowired
    private LogService logService;

    @Autowired
    private AuthUtil authUtil;

    @Autowired
    private UserClient userClient;

    public static GatewayLogUtil gatewayLogUtil;

    private final static String REQUEST_RECORDER_LOG_BUFFER = "RequestRecorderGlobalFilter.request_recorder_log_buffer";

    private final static String REQUEST_RECORDER_LOG_OBJ = "RequestRecorderGlobalFilter.obj";

    @PostConstruct
    public void init(){
        gatewayLogUtil = this;
        gatewayLogUtil.logService = this.logService;
        gatewayLogUtil.authUtil = this.authUtil;
        gatewayLogUtil.userClient = this.userClient;
    }

    private static boolean hasBody(HttpMethod method) {
        //只记录这3种谓词的body
        if (method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.PATCH)
            return true;

        return false;
    }

    private static boolean shouldRecordBody(MediaType contentType) {
        String type = contentType.getType();
        String subType = contentType.getSubtype();

        if ("application".equals(type)) {
            return "json".equals(subType) || "x-www-form-urlencoded".equals(subType) || "xml".equals(subType) || "atom+xml".equals(subType) || "rss+xml".equals(subType);
        } else if ("text".equals(type)) {
            return true;
        }

        //form没有记录
        return false;
    }

    private static Mono<Void> doRecordBody(String flag, LogObj logObj, StringBuffer logBuffer, Flux<DataBuffer> body, Charset charset) {
        return DataBufferUtilFix.join(body)
                .doOnNext(wrapper -> {
                    String bodyStr = new String(wrapper.getData(), charset);
                    logBuffer.append(bodyStr);
                    logBuffer.append("\n------------ end ------------\n\n");
                    wrapper.clear();

                    switch (flag) {
                        case "original":
                            logObj.getOriginalRequest().setRequestBody(bodyStr);
                            break;
                        case "route":
                            logObj.getRouteRequest().setRequestBody(bodyStr);
                            break;
                        case "respons":
                            logObj.getResponsInfo().setResponsBody(bodyStr);
                            System.out.println(logObj);
                            gatewayLogUtil.saveLog(logObj);
                            break;
                    }
                }).then();
    }

    private static Charset getMediaTypeCharset(@Nullable MediaType mediaType) {
        if (mediaType != null && mediaType.getCharset() != null) {
            return mediaType.getCharset();
        } else {
            return StandardCharsets.UTF_8;
        }
    }

    public static Mono<Void> recorderOriginalRequest(ServerWebExchange exchange) {
        StringBuffer logBuffer = new StringBuffer("\n---------------------------");

        LogObj logObj = new LogObj();
        logObj.setOriginalRequest(new RequestInfo());
        logObj.setRouteRequest(new RequestInfo());
        logObj.setResponsInfo(new ResponsInfo());

        exchange.getAttributes().put(REQUEST_RECORDER_LOG_BUFFER, logBuffer);

        exchange.getAttributes().put(REQUEST_RECORDER_LOG_OBJ, logObj);

        ServerHttpRequest request = exchange.getRequest();
        return recorderRequest("original", logObj, request, request.getURI(), logBuffer.append("\n原始请求：\n"));
    }

    public static Mono<Void> recorderRouteRequest(ServerWebExchange exchange) {
        URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
        StringBuffer logBuffer = exchange.getAttribute(REQUEST_RECORDER_LOG_BUFFER);

        LogObj logObj = exchange.getAttribute(REQUEST_RECORDER_LOG_OBJ);

        return recorderRequest("route", logObj, exchange.getRequest(), requestUrl, logBuffer.append("代理请求：\n"));
    }

    private static Mono<Void> recorderRequest(String flag, LogObj logObj, ServerHttpRequest request, URI uri, StringBuffer logBuffer) {
        if (uri == null) {
            uri = request.getURI();
        }

        HttpMethod method = request.getMethod();

        HttpHeaders headers = request.getHeaders();

        String ip = getIpAddress(request);

        logBuffer
                .append(method.toString()).append(' ')
                .append(uri.toString()).append('\n');

        logBuffer.append("------------请求头------------\n");
        Map<String, String> headersMap = new HashMap<>();

        headers.forEach((name, values) -> {
            values.forEach(value -> {
                headersMap.put(name, value);
                logBuffer.append(name).append(":").append(value).append('\n');
            });
        });

        String ua = headersMap.get("User-Agent");

        UserAgent userAgent = UserAgent.parseUserAgentString(ua);

        Browser browser = userAgent.getBrowser();

        OperatingSystem os = userAgent.getOperatingSystem();

        switch (flag) {
            case "original":
                logObj.getOriginalRequest().setRequestTime(new Date());
                logObj.getOriginalRequest().setUserAgentStr(ua);
                logObj.getOriginalRequest().setBrowser(browser);
                logObj.getOriginalRequest().setOperatingSystem(os);
                logObj.getOriginalRequest().setIp(ip);
                logObj.getOriginalRequest().setUri(uri);
                logObj.getOriginalRequest().setMethod(method);
                logObj.getOriginalRequest().setRequestHeaders(headersMap);
                break;
            case "route":
                logObj.getRouteRequest().setRequestTime(new Date());
                logObj.getRouteRequest().setUserAgentStr(ua);
                logObj.getRouteRequest().setBrowser(browser);
                logObj.getRouteRequest().setOperatingSystem(os);
                logObj.getRouteRequest().setIp(ip);
                logObj.getRouteRequest().setUri(uri);
                logObj.getRouteRequest().setMethod(method);
                logObj.getRouteRequest().setRequestHeaders(headersMap);
                break;
        }

        Charset bodyCharset = null;
        if (hasBody(method)) {
            long length = headers.getContentLength();
            if (length <= 0) {
                logBuffer.append("------------无body------------\n");
            } else {
                logBuffer.append("------------body 长度:").append(length).append(" contentType:");
                MediaType contentType = headers.getContentType();
                if (contentType == null) {
                    logBuffer.append("null，不记录body------------\n");
                } else if (!shouldRecordBody(contentType)) {
                    logBuffer.append(contentType.toString()).append("，不记录body------------\n");
                } else {
                    bodyCharset = getMediaTypeCharset(contentType);
                    logBuffer.append(contentType.toString()).append("------------\n");
                }
            }
        }


        if (bodyCharset != null) {
            return doRecordBody(flag, logObj, logBuffer, request.getBody(), bodyCharset);
        } else {
            logBuffer.append("------------ end ------------\n\n");
            return Mono.empty();
        }
    }

    public static Mono<Void> recorderResponse(ServerWebExchange exchange) {
        RecorderServerHttpResponseDecorator response = (RecorderServerHttpResponseDecorator) exchange.getResponse();
        StringBuffer logBuffer = exchange.getAttribute(REQUEST_RECORDER_LOG_BUFFER);

        LogObj logObj = exchange.getAttribute(REQUEST_RECORDER_LOG_OBJ);

        HttpStatus code = response.getStatusCode();

        logObj.getResponsInfo().setCode(code);

        if (code == null) {
            logBuffer.append("返回异常").append("\n------------ end ------------\n\n");
            return Mono.empty();
        }

        logBuffer.append("响应：").append(code.value()).append(" ").append(code.getReasonPhrase()).append('\n');

        HttpHeaders headers = response.getHeaders();
        logBuffer.append("------------响应头------------\n");

        Map<String, String> headersMap = new HashMap<>();

        headers.forEach((name, values) -> {
            values.forEach(value -> {
                headersMap.put(name, value);
                logBuffer.append(name).append(":").append(value).append('\n');
            });
        });
        logObj.getResponsInfo().setResponsHeaders(headersMap);

        Charset bodyCharset = null;
        MediaType contentType = headers.getContentType();
        if (contentType == null) {
            logBuffer.append("------------ contentType = null，不记录body------------\n");
        } else if (!shouldRecordBody(contentType)) {
            logBuffer.append("------------不记录body------------\n");
        } else {
            bodyCharset = getMediaTypeCharset(contentType);
            logBuffer.append("------------body------------\n");
        }

        if (bodyCharset != null) {
            return doRecordBody("respons", logObj, logBuffer, response.copy(), bodyCharset);
        } else {
            logBuffer.append("\n------------ end ------------\n\n");
            return Mono.empty();
        }
    }

    public static String getLogData(ServerWebExchange exchange) {
        StringBuffer logBuffer = exchange.getAttribute(REQUEST_RECORDER_LOG_BUFFER);
        return logBuffer.toString();
    }

    public static String getIpAddress(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String ip = headers.getFirst("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if (ip.indexOf(",") != -1) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
        }
        return ip;
    }

    public void saveLog(LogObj logObj) {
        if(logObj.getOriginalRequest().getUri().toString().contains("sys/login")){
            LoginRecord loginRecord = new LoginRecord();
            loginRecord.setIp(logObj.getOriginalRequest().getIp());
            loginRecord.setBrowserInfo(logObj.getOriginalRequest().getBrowser().getName().equals("Unknown") ? logObj.getOriginalRequest().getUserAgentStr() : logObj.getOriginalRequest().getBrowser().getName()+"/"+logObj.getOriginalRequest().getOperatingSystem().getName());
            loginRecord.setRequestTime(logObj.getOriginalRequest().getRequestTime());
            loginRecord.setUserAgentStr(logObj.getOriginalRequest().getUserAgentStr());
            loginRecord.setRes(logObj.getResponsInfo().getCode().getReasonPhrase());
            loginRecord.setUserName(JSONObject.parseObject(logObj.getOriginalRequest().getRequestBody()).getString("username"));
            gatewayLogUtil.logService.saveLoginRecord(loginRecord);
        }else if(!logObj.getOriginalRequest().getUri().toString().contains("users/current") && !logObj.getOriginalRequest().getUri().toString().contains("/log/")){
            LogRecord logRecord = new LogRecord();
            logRecord.setIp(logObj.getOriginalRequest().getIp());
            logRecord.setRequestTime(logObj.getOriginalRequest().getRequestTime());
            try {
                logRecord.setModelName(logObj.getOriginalRequest().getUri().getPath().substring(9,logObj.getOriginalRequest().getUri().getPath().lastIndexOf("/")));
            }catch (Exception e){
                logRecord.setModelName("");
            }
            try {
                logRecord.setOperating(logObj.getOriginalRequest().getUri().getPath().substring(logObj.getOriginalRequest().getUri().getPath().lastIndexOf("/")+1,logObj.getOriginalRequest().getUri().getPath().length()));
            }catch (Exception e){
                logRecord.setOperating("");
            }

            logRecord.setBrowserInfo(logObj.getOriginalRequest().getBrowser().getName().equals("Unknown") ? logObj.getOriginalRequest().getUserAgentStr() : logObj.getOriginalRequest().getBrowser().getName()+"/"+logObj.getOriginalRequest().getOperatingSystem().getName());
            logRecord.setUri(logObj.getOriginalRequest().getUri().toString());

            if(logObj.getOriginalRequest().getMethod() == HttpMethod.GET){
                logRecord.setRequestBody(logObj.getOriginalRequest().getUri().toString().substring(logObj.getOriginalRequest().getUri().toString().indexOf("?")+1));
            }else{
                logRecord.setRequestBody(logObj.getOriginalRequest().getRequestBody() == null ? "" : logObj.getOriginalRequest().getRequestBody());
            }
            logRecord.setCode(logObj.getResponsInfo().getCode().getReasonPhrase());
            //logRecord.setResponsBody(logObj.getResponsInfo().getResponsBody() == null ? "" : logObj.getResponsInfo().getResponsBody());


            String token = null;

            token = logObj.getOriginalRequest().getRequestHeaders().get("Authorization");

            if(null == token){
                token = logObj.getOriginalRequest().getRequestHeaders().get("authorization");
            }

            if(null == token){
                token = logObj.getOriginalRequest().getRequestHeaders().get("token");
            }

            if(null == token && logObj.getOriginalRequest().getUri().toString().indexOf("access_token=") >= 0){
                token = logObj.getOriginalRequest().getUri().toString().substring(logObj.getOriginalRequest().getUri().toString().indexOf("access_token=") + 13,logObj.getOriginalRequest().getUri().toString().indexOf("access_token=") + 49);
            }

            if(null != token && (token.contains(OAuth2AccessToken.BEARER_TYPE) || token.contains("bearer"))){
                token = token.substring(OAuth2AccessToken.BEARER_TYPE.length() + 1);
            }

            if(null == token){
                logRecord.setUserName("未获取到用户");
            }else{
                LoginAppUser loginAppUser = userClient.getLoginAppUserByToken(token);

                logRecord.setUserName(loginAppUser.getUsername());
            }


            gatewayLogUtil.logService.saveLogRecord(logRecord);
        }
    }
}
